技巧50 在构建中指定版本来避免软件包的漂移

Dockerfile语法简单,功能也有限,它们可以极大地帮助用户理清构建的需求,并且它们可以促进镜像生成的稳定性,但是它们无法确保可重复的构建结果。我们将会探索众多方案中的一个,借以解决这一问题并减少在底层包管理依赖关系改变时带来的意外风险。

本技巧有助于避免那些“它昨天还运行得好好的”的尴尬处境,如果你用过经典的配置管理工具就会感同身受。构建Docker镜像与维护一台服务器本质上有很大的不同,但是一些艰辛的教训依然适用。

注意

本技巧只适用于基于Debian的镜像,如Ubuntu。Yum用户可以找到相应包管理工具下的类似技术。

问题

想要确保deb包是自己期望的版本。

解决方案

在一个已验证安装的系统上运行一个脚本来抓取所有依赖软件包的版本。在Dockerfile里安装指定的版本,以确保所安装的版本和想要的完全一致。

针对版本方面的基本检查可以通过在一个已经验证过的系统上调用 apt-cache 来完成:

$ apt-cache show nginx | grep ^Version:
Version: 1.4.6-1ubuntu3

然后可以像下面这样在Dockerfile里指定版本:

RUN apt-get -y install nginx=1.4.6-1ubuntu3

这可能已经足以满足需求。但是这无法保证这一版本的nginx所安装的所有依赖都和之前验证的版本一致。可以通过在参数里添加一个 --recurse 标志来获取所有依赖的信息:

apt-cache --recurse depends nginx

这一命令的输出内容相当多,因此要获取版本需求的清单也是一件棘手的事情。幸好,我们维护了一个Docker镜像(还有其他的吗?)为用户提供方便。它会输出需要放到Dockerfile里 RUN 行的内容,以确保所有依赖的版本都是正确的:

$ docker run -ti dockerinpractice/get-versions vim
RUN apt-get install -y \
vim=2:7.4.052-1ubuntu3 vim-common=2:7.4.052-1ubuntu3 \
vim-runtime=2:7.4.052-1ubuntu3 libacl1:amd64=2.2.52-1 \
libc6:amd64=2.19-0ubuntu6.5 libc6:amd64=2.19-0ubuntu6.5 \
libgpm2:amd64=1.20.4-6.1 libpython2.7:amd64=2.7.6-8 \
libselinux1:amd64=2.2.2-1ubuntu0.1 libselinux1:amd64=2.2.2-1ubuntu0.1 \
libtinfo5:amd64=5.9+20140118-1ubuntu1 libattr1:amd64=1:2.4.47-1ubuntu1 \
libgcc1:amd64=1:4.9.1-0ubuntu1 libgcc1:amd64=1:4.9.1-0ubuntu1 \
libpython2.7-stdlib:amd64=2.7.6-8 zlib1g:amd64=1:1.2.8.dfsg-1ubuntu1 \
libpcre3:amd64=1:8.31-2ubuntu2 gcc-4.9-base:amd64=4.9.1-0ubuntu1 \
gcc-4.9-base:amd64=4.9.1-0ubuntu1 libpython2.7-minimal:amd64=2.7.6-8 \
mime-support=3.54ubuntu1.1 mime-support=3.54ubuntu1.1 \
libbz2-1.0:amd64=1.0.6-5 libdb5.3:amd64=5.3.28-3ubuntu3 \
libexpat1:amd64=2.1.0-4ubuntu1 libffi6:amd64=3.1~rc1+r3.0.13-12 \
libncursesw5:amd64=5.9+20140118-1ubuntu1 libreadline6:amd64=6.3-4ubuntu2 \
libsqlite3-0:amd64=3.8.2-1ubuntu2 libssl1.0.0:amd64=1.0.1f-1ubuntu2.8 \
libssl1.0.0:amd64=1.0.1f-1ubuntu2.8 readline-common=6.3-4ubuntu2 \
debconf=1.5.51ubuntu2 dpkg=1.17.5ubuntu5.3 dpkg=1.17.5ubuntu5.3 \
libnewt0.52:amd64=0.52.15-2ubuntu5 libslang2:amd64=2.2.4-15ubuntu1 \
vim=2:7.4.052-1ubuntu3

在某些时候,用户的构建将会因为版本不再可用而失败。遇到这种情况时可以看看有哪些包做了改动,然后重新检查一下这些改动,确认是否满足特定镜像的需求。

这个例子假定用的镜像是ubuntu:14.04。如果用的是Debian的不同发行版,那么可以fork该仓库并修改Dockerfile里的 FROM 指令,并构建它。该仓库可以在https://github.com/docker-in- practice/get-versions.git找到。

尽管本技巧有助于提升构建的稳定性,但它在安全性方面没有任何建树,因为用户仍然需要从一个无法直接控制的仓库下载软件包。

讨论

本技巧似乎费了很大的劲来保证一个文本编辑器和你的预期一致。然而,在实战中,软件包漂移会引起极难定位的bug。软件库和应用程序在每天的构建中都会有微妙的变化,试图找出哪里变化了可能会花费你一天的时间。

通过在Dockerfile中尽可能详细地指定版本,你可以确信只会发生下面两种情况中的一种:要么是构建成功了,软件和昨天表现的行为一致;要么由于一部分软件变化导致构建失败,需要重走一遍开发测试流程。在第二种情况下,读者可以意识到哪里有变动,并且可以将排障范围缩小到任何在此变动之后出现的问题。

这里的关键在于当读者使用持续构建和集成时,减少变化的变量数量就能减少花在调试上的时间,而这可以为用户的业务带来金钱上的收益。

results matching ""

    No results matching ""